/* Copyright (C) 2018 Ariadne Devos

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
#ifndef _sHT_BLOCK_H
#define _sHT_BLOCK_H

#include <stddef.h>
#ifndef _WIN32
# include <sys/mman.h>
#endif

/** Memory blocks

  A read-write byte region backed by physical memory, contiguously addressable
  from virtual memory. On their own, they do not have a particular layout or
  type.

  To be allocated efficiently, space and time-wise, they should be at
  the very least one page large (4KiB on x86). (However, within the kernel,
  it should usually be exactly a single page, see Linux
  Documentation/flexible-arrays.txt).

  Allocation can be done in batch using @var{sHT_block_alloc_batch} and
  @var{sHT_block_free_batch}, or one at a time with @var{sHT_block_alloc}
  and @var{sHT_block_free}.

  This API is effectively an abstraction with a specification around:

  Unix userspace: mmap(2) or malloc(3).
  W32: VirtualAlloc(2).
  Linux kernel: alloc_pages (requires little pages). */

#ifndef _WIN32
/** A special value indicating a memory allocation failure, returned
  by @var{sHT_block_alloc}. It is not necessarily @code{NULL}.

  TODO: this is NULL on W32
  (<https://msdn.microsoft.com/en-us/library/Aa366887(v=VS.85).aspx>). */
#define sHT_BLOCK_ALLOC_FAILED (MAP_FAILED)
#else
#define sHT_BLOCK_ALLOC_FAILED (NULL)
#endif

/** A minimal alignment of a blocks address.

  The actual alignment may be higher. */
#define sHT_BLOCK_ALIGN 4096

/** Try to allocate a memory block

  @var{size}: a positive number, a minimum on the number of bytes of the block

  If the function call is cancelled (e.g. by pthread_cancel(3)) or jumped over
  and not restored (e.g. by longjmp(3) from a signal handler), a block of
  memory of @var{size} bytes or a bit more may be leaked within the process.

  On an out-of-memory condition, the despeculated return value is
  @var{sHT_BLOCK_ALLOC_FAILED}. Else, it is a fresh block that can be freed
  by @var{sHT_block_free}. */
void *
sHT_block_alloc(size_t size);

/** Free a single memory block

  @var{block}: the block to free
  @var{size}: the size of the block

  On a speculative execution, the block might actually not be freed.

  If cancelled (e.g. by pthread_cancel(3)) or jumped over (e.g. by longjmp(3)
  from a signal handler), the block may or may not be freed, resulting in a
  memory leak within the process of at most @var{size} plus delta. */
void
sHT_block_free(void *block, size_t size);

/** Try to allocate @var{n} memory blocks of particular sizes

  @var{n}: the length of @var{dest} and @var{sizes}
  @var{dest}: a list of block pointers to write. May not be accessed
    concurrently.
  @var{sizes}: a list of the positive sizes of each block to allocate, in the
    same order as @var{dest}. May not be modified.

  On a speculative execution, more or less blocks may actually be allocated
  and the return value may be incorrect. On a non-speculative execution,
  a return value of 1 indicates that the blocks could not be allocated as
  requested. Then there is no net change is memory consumption except for
  some change. 0 indicates all blocks were allocated.

  If cancelled (e.g. by pthread_cancel(3)) or jumped over (e.g. by longjmp(3)
  from a signal handler), there may be a memory leak within the process of at
  most the sum of @var{sizes} plus delta. */
_Bool
sHT_block_alloc_batch(size_t n, void *dest[], const size_t sizes[]);

/** Free the memory blocks in @var{dest}

  @var{n}: the number of elements in @var{dest} and @var{sizes}
  @var{dest}: a readable list of distinct, allocated blocks to free.
    May not be accessed concurrently.
  @var{sizes}: a readable list of the size of each block of @var{dest},
    in the same order. May not be modified concurrently.

  Some elements of @var{dest} are freed. On a non-speculative execution,
  all are freed.

  If cancelled (e.g. by pthread_cancel(3)) or jumped over (e.g. by longjmp(3)
  from a signal handler), there may be a memory leak within the process of at
  most the sum of @var{sizes} plus delta.

  On a non-speculative execution, all are freed. Otherwise, it might only be
  a subset. */
void
sHT_block_free_batch(size_t n, void *const dest[], const size_t sizes[]);

#endif
